// 我们看过了 rag 这个部分
// 设想一个问题： llm 始终是根据已有的数据去 train 出来的，对于我们自身业务的数据，我可以通过 rag 取增强，但是对于一些实时的东西呢？
// 举个例子： 我想知道今天天气怎么样，llm 是没有办法的，而且，“今天”不是固定的，我总不能每天定时取更新 rag 的输入数据吧？
// 更加合理的是，我告诉 llm 一个可以获取今天天气的 api，然后让 llm 自己取调用这个 api，获取到天气的数据，然后再根据天气的数据给出回答
// 这就是 function calling 的用处
// 当然，现在 function calling 有个新名称：tools

// 我们先浅看一下 openAi给的 tools 的定义：

const tools = [
    {
      type: "function",
      function: {
        // 这个 name 是用来告诉 llm 这个 tool 的名称是什么
        name: "getCurrentWeather",
        // llm 会根据这个 description 来决定是否使用这个 tool
        description: "Get the current weather in a given location",
        // 这个 parameters 是用来告诉 llm 这个 tool 的输入参数是什么
        parameters: {
          type: "object",
          properties: {
            location: {
              type: "string",
              description: "The city and state, e.g. San Francisco, CA",
            },
            unit: { type: "string", enum: ["celsius", "fahrenheit"] },
          },
          required: ["location"],
        },
      },
    }
]
// 其实就是给了个可解析的 json 对象，然后 llm 会根据这个对象来决定是否使用这个 tool
// 进一步的，我们来看一下 langchain 对于 tools 的封装吧

// 在此之前，我们先学一下 zod 的使用，它可以用于定义 tool 函数的JSON schema，我们可以专注在参数的描述上，参数的类型定义和是否 required 都可以有 zod 来生成


import { z } from "zod";
import { zodToJsonSchema } from "zod-to-json-schema";
import { useModel } from "../model";
import { tool } from "@langchain/core/tools";
import { config } from "dotenv";
await config();

const stringSchema = z.string()
stringSchema.parse("hello zod!")

// stringSchema.parse(123)
/**
 * ZodError: [
  {
    "expected": "string",
    "code": "invalid_type",
    "path": [],
    "message": "Invalid input: expected string, received number"
  }
]
  这个报错的可读性非常高，可以然 llm 自行分析并给出修复建议
 */

const getCurWeatherSchema = z.object({
    location: z.string().describe("The city and state, e.g. San Francisco, CA"),
    unit: z.enum(["celsius", "fahrenheit"]).describe("The unit of temperature, either Celsius or Fahrenheit"),
})

// 提取信息的函数
const taggingSchema = z.object({
    emotion:z.enum(["pos", "neg", "neutral"]).describe("文本的情感"),
    language: z.string().describe("文本的核心语言（应为ISO 639-1代码）"),
});

const taggingTool = tool(
    async(input: any) => {
        return JSON.stringify(input)
    },
    {
        name: "tagging",
        description: "为特定的文本片段打上标签",
        schema: taggingSchema,
    }
)

const model = useModel({})
const modelTagging = model.bindTools([taggingTool],{
    tool_choice: "tagging"
})
import { JsonOutputToolsParser } from "langchain/output_parsers";
import { ChatPromptTemplate } from "@langchain/core/prompts";

const prompt = ChatPromptTemplate.fromMessages([
    ["system", "仔细思考，你有充足的时间进行严谨的思考，然后按照指示对文本进行标记"],
    ["human", "{input}"]
])

const chain = prompt.pipe(modelTagging).pipe(new JsonOutputToolsParser())
const res = await chain.invoke({
    input: "今天天气不错，心情很好, 但是我在工作中遇到了一些问题，心情又变得很糟糕了"
})
// console.log(res)
// [ { type: 'tagging', args: { emotion: 'neg', language: 'zh' } } ]



// 好的，我们再来一个信息提取的例子吧
const getPersonInfoSchema = z.object({
    infos: z.array(z.object({
        name: z.string().describe("人的姓名"),
        age: z.number().optional().describe("人的年龄，如果文本中没有明确提到或无法推断，可以不填"),
        gender: z.enum(["male", "female"]).optional().describe("人的性别"),
        hobby: z.array(z.string()).optional().describe("人的爱好。需要仔细分析文本，不仅包括直接提到的爱好，还要从上下文推断（例如：喜欢羽毛球拍 - 说明可能喜欢打羽毛球）"),
    })).describe("提取主要人物们的信息"),
    relations: z.string().describe("人物之间的详细关系列表。要仔细分析每个人对其他人的关系，包括隐含的情感关系"),
})

const getPersonInfoTool = tool(
    async(input: any) => {
        return JSON.stringify(input)
    },
    {
        name: "getPersonInfo",
        description: "从文本中提取所有人物的详细信息（姓名、年龄、性别、爱好）和人物之间的所有关系（包括明确的社会关系如同学朋友，以及隐含的情感关系如暗恋追求）。重点要通过行为推断情感：给某人买礼物、追求等行为通常暗示暗恋或追求关系。",
        schema: getPersonInfoSchema,
    }
)

const modelGetPersonInfo = model.bindTools([getPersonInfoTool],{
    tool_choice: "getPersonInfo"
})

const promptGetPersonInfo = ChatPromptTemplate.fromMessages([
    ["system", "仔细分析文本，提取人物信息和人物之间的关系。重点注意：\n1. 通过物品推断爱好（如：喜欢篮球鞋→爱打篮球）\n2. 通过行为推断关系（如：给某人买礼物、追求→暗恋/追求关系）\n3. 要提取所有人物之间的关系，包括情感关系（暗恋、追求等）和社会关系（同学、朋友等）"],
    ["human", "{input}"]
])

const chainGetPersonInfo = promptGetPersonInfo.pipe(modelGetPersonInfo).pipe(new JsonOutputToolsParser())
const resGetPersonInfo = await chainGetPersonInfo.invoke({
    input: "我有个叫张三的朋友，他是个 gay，去年刚成年。然后有个王八的同级女生不知道他是 gay，还傻乎乎的去追他，给他买了他最爱的篮球鞋，却碰了一鼻子灰"
})
console.log(JSON.stringify(resGetPersonInfo, null, 2))
/**
[
  {
    "type": "getPersonInfo",
    "args": {
      "infos": [
        {
          "age": 18,
          "gender": "male",
          "hobby": [
            "篮球"
          ],
          "name": "张三"
        },
        {
          "age": null,
          "gender": "female",
          "hobby": [],
          "name": "王八"
        }
      ],
      "relations": "张三和王八是同学关系。王八暗恋张三，并给张三买了他最爱的篮球鞋作为礼物，但因为张三是gay，所以对王八没有兴趣。"
    }
  }
]
 */